package org.joot.jmir2.texture.loader;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.apache.log4j.Logger;
import org.joot.jmir2.display.Graphics;
import org.joot.jmir2.exception.JMir2Exception;
import org.joot.jmir2.texture.Texture;
import org.joot.jmir2.util.Files;
import org.joot.jmir2.util.Misc;
import org.joot.jmir2.util.Wils;
import org.joot.jmir2.util.Wils.WILImageInfo;
import org.lwjgl.opengl.EXTTextureMirrorClamp;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GLContext;

/**
 * 纹理加载任务
 * <br>
 * 即实际上处理纹理加载的线程主函数承载对象
 * <br>
 * 会不会太专业了(-_-)
 * 
 * @author ShawRyan
 */
public class TextureLoadingTask implements Callable<ByteBuffer>{
	/** log */
	private static final Logger log = Logger.getLogger(TextureLoadingTask.class);
	
	/** 图片描述信息 */
	private WILImageInfo wii;
	/** 纹理索引 */
	private Integer index = null;
	/** 加载任务 */
	private Future<ByteBuffer> loadFuture = null;
	/** 线程池 */
	private ExecutorService threadPool = null;
	/** 纹理 */
	private Texture tex = null;
	
	/**
	 * 构造函数
	 * <br>
	 * 使用纹理索引来加载纹理
	 * <br>
	 * 如纹理图片信息未加入缓存，则可只给出纹理索引
	 * 
	 * @param index
	 * 		纹理索引
	 * @param threadPool
	 * 		执行线程池
	 */
	public TextureLoadingTask(Integer index, ExecutorService threadPool) {
		this.index = index;
		this.threadPool = threadPool;
	}
	/**
	 * 构造函数
	 * <br>
	 * 在图片信息被缓存时使用此种构造函数，减轻程序读取压力
	 * 
	 * @param index
	 * 		纹理索引
	 * @param wii
	 * 		纹理图片描述信息
	 * @param threadPool
	 * 		执行线程池
	 */
	public TextureLoadingTask(Integer index, WILImageInfo wii, ExecutorService threadPool) {
		this.index = index;
		this.threadPool = threadPool;
		this.wii = wii;
	}
	
	/**
	 * 加载纹理
	 * 
	 * @return 是否完成加载
	 */
	public boolean update() {
		if(loadFuture == null)
			loadFuture = threadPool.submit(this);
		else if(loadFuture.isDone()) {
			try {
				ByteBuffer datas = loadFuture.get();
				/* 绑定数据 */
				tex = new Texture2DImpl(GL11.glGenTextures(), wii.getWidth(), wii.getHeight(), index);
				GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
				GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		        if(GLContext.getCapabilities().GL_EXT_texture_mirror_clamp) {
		        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_EDGE_EXT);
		        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_EDGE_EXT);
		        } else {
		        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
		        	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
		        }
				
				// 绑定纹理数据到纹理id
				tex.bind();
		        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, wii.getWidth(), wii.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, datas);
			} catch (Exception ex) {
				JMir2Exception exception = JMir2Exception.valueOf(ex, JMir2Exception.EXCEPTION_TYPE_CALC);
				log.error(exception.getMessage(), ex);
				throw exception;
			}
		}
		return tex != null;
	}

	/** 本次加载得到的图片信息 */
	public WILImageInfo getImageInfo() {
		return wii;
	}
	/** 获取纹理索引 */
	public Integer getIndex() {
		return index;
	}
	/** 读取加载的纹理 */
	public Texture getTex() {
		return tex;
	}
	
	/**
	  * 真正用于纹理加载的方法
	  * <br>
	  * 此方法返回字节数组，字节数组为RGBA格式的色彩数据
	  * <br>
	  * 在整个程序中实际上只有这个函数是<b>另起线程</b>，是<b>异步</b>的。所以在这个方法里不能使用{@link GL11 GL}的函数，因为线程内没有{@link GLContext context}
	  * <br>
	  * 
	  * @return 纹理数据<br>此数据为直接分配物理内存的指针，便于显卡读取
	  */
	@Override
	public ByteBuffer call() throws Exception {
		int textureType = (index & 0xff000000) >> 24;
		int textureFileIndex = (index & 0xff0000) >> 16;
		int textureIndex = index & 0xffff;
		RandomAccessFile raf = null;
		switch(textureType) {
		case Texture.TEXTURE_TYPE_TILES:
			raf = Files.randomAccessFile("/data/Tiles.wil");
			if(this.wii == null)
				this.wii = Wils.get(textureIndex, "/data/Tiles.wil");
			break;
		case Texture.TEXTURE_TYPE_SMTILES:
			raf = Files.randomAccessFile("/data/SmTiles.wil");
			if(this.wii == null)
				this.wii = Wils.get(textureIndex, "/data/SmTiles.wil");
			break;
		case Texture.TEXTURE_TYPE_OBJECTS:
			raf = Files.randomAccessFile("/data/Objects" + (textureFileIndex == 0 ? "" : textureFileIndex) + ".wil");
			if(this.wii == null)
				this.wii = Wils.get(textureIndex, "/data/Objects" + (textureFileIndex == 0 ? "" : textureFileIndex) + ".wil");
			break;
		default:
			break;
		}
		
		int width = wii.getWidth();
		int height = wii.getHeight();
		int offset = wii.getDataStart();
		int length = wii.getDataSize();
		
		// 兼容老显卡，一般来说版本大于或等于OpenGL2.0的显卡都支持非2的幂次宽高纹理
		int nWidth = width;
		int nHeight = height;
		if(!Graphics.get().isNonpoweroftwoSupport()) {
			if(!Misc.isPowerOfTwo(width)) width = Misc.closestPowerOfTwo(width);
			if(!Misc.isPowerOfTwo(height)) height = Misc.closestPowerOfTwo(height);
			wii.setWidth((short) width);
			wii.setHeight((short) height);
		}
		
		// 创建字节缓存，使用直接分配内存方式
		ByteBuffer unpackedPixels = ByteBuffer.allocateDirect(width * height * 4);
		unpackedPixels.order(ByteOrder.nativeOrder());
		
		if(length < 13) {
			// 如果是空白图片
			for(int i = 0; i < width * height; ++i) {
				unpackedPixels.put(Misc.palletes[0][1]);
				unpackedPixels.put(Misc.palletes[0][2]);
				unpackedPixels.put(Misc.palletes[0][3]);
				unpackedPixels.put(Misc.palletes[0][0]);
			}
		} else {
			byte[] pixels = new byte[length];
			try {
				raf.seek(offset + 8);
				raf.read(pixels);
			} catch(IOException ex) {
				JMir2Exception exception = JMir2Exception.valueOf(ex, JMir2Exception.EXCEPTION_TYPE_FILESTREAM);
				log.error(exception.getMessage(), ex);
				throw exception;
			}
			if(wii.getColorCount() == 8) {
				int p_index = 0;
				for(int h = height - 1; h >= height - nHeight; --h)
					for(int w = width - nWidth; w < width; ++w) {
						// 跳过填充字节
						if(w == width - nWidth)
							p_index += Misc.skipBytes(8, nWidth);
						// 坐标转换，bmp中图片数据是从左到右，从下到上，而我们需要从下到上
						unpackedPixels.put((h * width + w) * 4, Misc.palletes[pixels[p_index] & 0xff][1]);
						unpackedPixels.put((h * width + w) * 4 + 1, Misc.palletes[pixels[p_index] & 0xff][2]);
						unpackedPixels.put((h * width + w) * 4 + 2, Misc.palletes[pixels[p_index] & 0xff][3]);
						unpackedPixels.put((h * width + w) * 4 + 3, Misc.palletes[pixels[p_index++] & 0xff][0]);
					}
			} else if(wii.getColorCount() == 16) {
				int p_index = 0;
				for(int h = height - 1; h >=  height - nHeight; --h)
					for(int w = width - nWidth; w < width; ++w, p_index += 2) {
						// 跳过填充字节
						if(w == width - nWidth)
							p_index += Misc.skipBytes(16, nWidth);
						// 坐标转换，bmp中图片数据是从左到右，从下到上，而我们需要从下到上
						// 获取短整型的色彩数据(565压缩过)
						if(pixels[p_index + 1] == 0 && pixels[p_index] == 0) {
							// 暂时不手动填充，因为默认分配内存即为0
							/*unpackedPixels.put((h * width + w) * 4, (byte) 0);
							unpackedPixels.put((h * width + w) * 4 + 1, (byte) 0);
							unpackedPixels.put((h * width + w) * 4 + 2, (byte) 0);
							unpackedPixels.put((h * width + w) * 4 + 3, (byte) 0);*/
						}
						else {
							unpackedPixels.put((h * width + w) * 4, (byte) (pixels[p_index + 1] & 0xF8));
							unpackedPixels.put((h * width + w) * 4 + 1, (byte) (((pixels[p_index + 1] & 0x7) << 5) | ((pixels[p_index] & 0xE0) >> 3)));
							unpackedPixels.put((h * width + w) * 4 + 2, (byte) ((pixels[p_index] & 0x1F) << 3));
							unpackedPixels.put((h * width + w) * 4 + 3, (byte) 255);
						}
					}
			}
		}
		
		// fix LWJGL org.lwjgl.BufferChecks#checkBuffer
		unpackedPixels.position(0);
		
		return unpackedPixels;
	}
	
	/**
	 * 纹理贴图
	 * 
	 * @author ShawRyan
	 *
	 */
	public class Texture2DImpl implements Texture {
		
		/** 宽度 */
		private int width;
		/** 高度 */
		private int height;
		/** 索引 */
		private Integer ref;
		/** 纹理ID */
		private int textureId;
		
		Texture2DImpl() { }
		Texture2DImpl(int textureId, int width, int height, Integer ref) {
			this.textureId = textureId;
			this.width = width;
			this.height = height;
			this.ref = ref;
		}
		
		public Integer getTextureRef() {
			return this.ref;
		}

		public void bind() {
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.textureId);
		}

		public void release() {
			/*ByteBuffer temp = ByteBuffer.allocateDirect(4);
		    temp.order(ByteOrder.nativeOrder());
		    IntBuffer texBuf = temp.asIntBuffer();
		    texBuf.put(this.textureId);
	        texBuf.flip();
	        glDeleteTextures(texBuf);*/
			GL11.glDeleteTextures(this.textureId);
		}
		
		public int getHeight() {
			return this.height;
		}

		public int getWidth() {
			return this.width;
		}

		public int getTextureID() {
			return this.textureId;
		}
	}
}